import atexit
import os
import shutil
import socket
import subprocess
import time

import pytest

from bitmapist import delete_all_events, setup_redis


@pytest.fixture(scope="session")
def redis_settings():
    # Find the first redis-server in PATH, fallback to /usr/bin/redis-server
    default_path = shutil.which("redis-server") or "/usr/bin/redis-server"
    return {
        "server_path": os.getenv("BITMAPIST_REDIS_SERVER_PATH", default_path),
        "port": int(os.getenv("BITMAPIST_REDIS_PORT", "6399")),
    }


@pytest.fixture(scope="session", autouse=True)
def redis_server(redis_settings):
    """Fixture starting the Redis server"""
    redis_host = "127.0.0.1"
    redis_port = redis_settings["port"]
    if is_socket_open(redis_host, redis_port):
        yield None
    else:
        proc = start_redis_server(redis_settings["server_path"], redis_port)
        # Give Redis a moment to start up
        time.sleep(0.1)
        wait_for_socket(redis_host, redis_port)
        yield proc
        proc.terminate()


@pytest.fixture(scope="session", autouse=True)
def setup_redis_for_bitmapist(redis_settings):
    setup_redis("default", "localhost", redis_settings["port"])
    setup_redis("default_copy", "localhost", redis_settings["port"])
    setup_redis("db1", "localhost", redis_settings["port"], db=1)


@pytest.fixture(autouse=True)
def clean_redis():
    delete_all_events()


def start_redis_server(server_path, port):
    """Helper function starting Redis server"""
    devzero = open(os.devnull)
    devnull = open(os.devnull, "w")
    command = get_redis_command(server_path, port)
    proc = subprocess.Popen(
        command,
        stdin=devzero,
        stdout=devnull,
        stderr=devnull,
        close_fds=True,
    )
    atexit.register(lambda: proc.terminate())
    return proc


def get_redis_command(server_path, port):
    """Run with --version to determine if this is redis or bitmapist-server"""
    output = subprocess.check_output([server_path, "--version"])
    if b"bitmapist-server" in output:
        return [server_path, "-addr", f"0.0.0.0:{port}"]
    return [server_path, "--port", str(port)]


def is_socket_open(host, port):
    """Helper function which tests is the socket open"""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(0.1)
    return sock.connect_ex((host, port)) == 0


def wait_for_socket(host, port, seconds=3):
    """Check if socket is up for :param:`seconds` sec, raise an error otherwise"""
    polling_interval = 0.1
    iterations = int(seconds / polling_interval)

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(0.1)
    for _ in range(iterations):
        result = sock.connect_ex((host, port))
        if result == 0:
            sock.close()
            break
        time.sleep(polling_interval)
    else:
        raise RuntimeError(f"Service at {host}:{port} is unreachable")
